﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace CodeWork.Chat
{
    [DefaultProperty("Text")]
    [ToolboxData("<{0}:ChatRoom runat=server></{0}:ChatRoom>")]
    public class ChatRoom : WebControl, INamingContainer, ICallbackEventHandler
    {
        //private variables
        //private System.Collections.Generic.Queue _Messages<ChatMessage>;
        Queue<ChatMessage> _messages = new Queue<ChatMessage>();
        private long _lastMsgID = 0;
        private Panel pnlRoomWindow = new Panel();
        private TextBox tbxMessageInput = new TextBox();

        #region Properties

        [Bindable(true)]
        [Category("Appearance")]
        [DefaultValue("")]
        [Localizable(true)]
        public string Text
        {
            get
            {
                String s = (String)ViewState["Text"];
                return ((s == null) ? "[" + this.ID + "]" : s);
            }

            set
            {
                ViewState["Text"] = value;
            }
        }

        private int _messageStoreLimit = 10;
        [Bindable(true)]
        [Category("Behavior")]
        [DefaultValue(10)]
        [Localizable(true)]
        [Description("This is the maximum message storage capacity for the chat room")]
        public int MessageStoreLimit
        {
            get { return this._messageStoreLimit; }
            set 
            {
                const int MINIMUM_LIMIT = 1;
                const int MAXIMUM_LIMIT = 100;

                if (value < MINIMUM_LIMIT)
                    this._messageStoreLimit = MINIMUM_LIMIT;
                else if (value > MAXIMUM_LIMIT)
                    this._messageStoreLimit = MAXIMUM_LIMIT;
                else
                    this._messageStoreLimit = value; 
            }
        }

        private int _refreshRate = 2;
        [Bindable(true)]
        [Category("Behavior")]
        [DefaultValue(10)]
        [Localizable(true)]
        [Description("This is the duration in seconds after which the room will refresh")]
        public int RefreshRate
        {
            get { return this._refreshRate; }
            set
            {
                const int MINIMUM_RATE = 1;
                const int MAXIMUM_RATE = 10;

                if (value < MINIMUM_RATE)
                    this._refreshRate = MINIMUM_RATE;
                else if (value > MAXIMUM_RATE)
                    this._refreshRate = MAXIMUM_RATE;
                else
                    this._refreshRate = value;
            }
        }

        private bool _initialFocus = true;
        [Bindable(true)]
        [Category("Behavior")]
        [DefaultValue(true)]
        [Localizable(true)]
        [Description("This control gets focus upon page load if true")]
        public bool InitialFocus
        {
            get { return this._initialFocus; }
            set { this._initialFocus = value; }
        }

        [Bindable(true)]
        [Category("Behavior")]
        [DefaultValue("Anonymous")]
        [Localizable(true)]
        [Description("This is the default username for the chatter")]
        public string UserName
        {
            get 
            {
                const string DEFAULT_USERNAME = "Anonymous";

                string userName = (string)ViewState["UserName"];

                if (string.IsNullOrEmpty(userName))
                    return DEFAULT_USERNAME;
                else
                    return userName; 
            }
            set { ViewState["UserName"] = value; }
        }

        [Bindable(true)]
        [Category("Behavior")]
        [DefaultValue("General")]
        [Localizable(true)]
        [Description("This control gets focus upon page load if true")]
        public string ChatTopic
        {
            get 
            {
                const string DEFAULT_CHAT_TOPIC = "General";

                string chatTopic = (string)ViewState["ChatTopic"];

                if (string.IsNullOrEmpty(chatTopic))
                    return DEFAULT_CHAT_TOPIC;
                else
                    return chatTopic;
            }
            set { ViewState["ChatTopic"] = value; }
        }

        #endregion

        public ChatRoom()
        {
            //set default size
            base.Width = new Unit(550, UnitType.Pixel);
            base.Height = new Unit(400, UnitType.Pixel);
            this.Load += OnLoad;
        }

        protected override void RenderContents(HtmlTextWriter output)
        {
            double originalWidth = this.Width.Value;
            double originalHeight = this.Height.Value;

            // Creating the control layout table
            Table controlLayout = new Table();
            controlLayout.Width = this.Width;
            controlLayout.Height = this.Height;
            controlLayout.ToolTip = this.ToolTip;
            controlLayout.Style.Add(HtmlTextWriterStyle.Overflow, "scroll");
            controlLayout.Style.Add(HtmlTextWriterStyle.Position, "absolute");
            controlLayout.Style.Add(HtmlTextWriterStyle.BorderWidth, "1px");

            TableRow topRow = new TableRow();

            TableCell roomWindowCell = new TableCell();
            roomWindowCell.ColumnSpan = 2;
            topRow.Cells.Add(roomWindowCell);

            TableRow bottomRow = new TableRow();

            TableCell messageInputCell = new TableCell();
            bottomRow.Cells.Add(messageInputCell);

            TableCell sendButtonCell = new TableCell();
            bottomRow.Cells.Add(sendButtonCell);

            controlLayout.Rows.Add(topRow);
            controlLayout.Rows.Add(bottomRow);

            // Setting the room window property
            pnlRoomWindow.Style.Add(HtmlTextWriterStyle.BorderStyle, "solid");
            pnlRoomWindow.Style.Add(HtmlTextWriterStyle.BorderWidth, "1px");
            pnlRoomWindow.Style.Add(HtmlTextWriterStyle.BorderColor, "#C3DBDF");
            pnlRoomWindow.Width = new Unit(this.Width.Value - 2, UnitType.Pixel);
            pnlRoomWindow.Height = new Unit(this.Height.Value - 30, UnitType.Pixel);
            pnlRoomWindow.Style[HtmlTextWriterStyle.Overflow] = "scroll";

            // Setting the send button
            Button btnMessageSend = new Button();
            btnMessageSend.ID = this.ID + "_btnMessageSend";
            btnMessageSend.Width = new Unit(90, UnitType.Pixel);
            btnMessageSend.Height = new Unit(30, UnitType.Pixel);
            btnMessageSend.UseSubmitBehavior = false;
            btnMessageSend.Text = "Send";

            // Setting the input message box
            tbxMessageInput.TextMode = TextBoxMode.SingleLine;
            tbxMessageInput.Width = new Unit(this.Width.Value - 100, UnitType.Pixel);
            tbxMessageInput.Height = new Unit(20, UnitType.Pixel);
            tbxMessageInput.Attributes["autocomplete"] = "off";
            tbxMessageInput.Attributes["onkeydown"] = "if ((event.keyCode == 13)) {document.getElementById('" + btnMessageSend.ClientID + "').click();return false;} else return true;";
            tbxMessageInput.Attributes["onkeypress"] = "if (event.keyCode==124 || event.keyCode==126) event.keyCode=0;"; //prevent reserved (delimiter) characters from being entered

            btnMessageSend.Attributes["OnClick"] = "DoChatCallBack(document.getElementById('" + tbxMessageInput.ClientID + "'));";

            roomWindowCell.Controls.Add(pnlRoomWindow);

            messageInputCell.Controls.Add(tbxMessageInput);

            sendButtonCell.Controls.Add(btnMessageSend);

            controlLayout.RenderControl(output);
        }

        protected void OnLoad(object sender, System.EventArgs e)
        {
            // Setting the IDs to be pre initialized for global use
            pnlRoomWindow.ID = this.ID + "_pnlRoomWindow";
            tbxMessageInput.ID = this.ID + "_tbxMessageInput";

            if (this.Visible)
            {
                string sCallBack = codework_chat.Properties.Resources.Callback.Replace("WebChat1", this.ID);
                if (this.RefreshRate != 2)
                {
                    sCallBack = sCallBack.Replace("2000", (this.RefreshRate * 1000).ToString());
                }
                Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "CalledBack", sCallBack, true);

                //Now generate the function that initiates 
                //the client side callback
                StringBuilder sb = new StringBuilder();
                sb.Append(System.Environment.NewLine);
                sb.Append("function DoChatCallBack(txt)");
                sb.Append(System.Environment.NewLine);
                sb.Append("{");
                sb.Append("var msg = ''; if (txt != null) msg=txt.value; ");
                sb.Append(System.Environment.NewLine);
                sb.Append(Page.ClientScript.GetCallbackEventReference(this, "WebChatMaxMsgID + '||' + msg", "CalledBack", "ErrCalledBack"));
                sb.Append(";");
                sb.Append(System.Environment.NewLine);
                sb.Append("if (txt != null) txt.value='';");
                sb.Append(System.Environment.NewLine);
                sb.Append("}");
                string sDoCallBack = sb.ToString();
                Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "DoChatCallBack", sDoCallBack, true);

                //set initial focus (if configured to do so)
                if (this.InitialFocus && this.Enabled)
                {
                    Page.ClientScript.RegisterStartupScript(this.GetType(), "ChatFocus", "document.getElementById('" + tbxMessageInput.ClientID + "').focus();", true);
                }

                //emit JavaScript function call that initializes 
                //the control and joins the user into the conversation
                Page.ClientScript.RegisterStartupScript(this.GetType(), "ChatEnter", "ChatEnter();", true);
            }
        }

        #region AJAX Handling

        public delegate void ChatterEventHandler(ref string ChatText);
        public event ChatterEventHandler Chatter;

        void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
        {
            RaiseCallbackEvent(eventArgument);
        }

        public void RaiseCallbackEvent(string eventArgument)
        {
            //extract the last message id that was received by the client
            //so that we can pass back only the new messages
            string[] aryArgs = eventArgument.Split("||".ToCharArray());

            _lastMsgID = long.Parse(aryArgs[0]);
            eventArgument = aryArgs[2].Trim();

            string WebChatTopic = "WebChat_Conversation_" + this.ChatTopic;

            //Raise Chatter event if new message was sent
            if (eventArgument.Length > 0)
            {
                if(Chatter != null)
                    Chatter(ref eventArgument);
            }

            //Massage the message cosmetically
            string ModifiedMsgText = "<b><i>" + this.UserName + "</i></b>: " + eventArgument;

            //Does the conversation need instantiation?
            if (Context.Application[WebChatTopic] == null)
            {
                //no existing queue found so create a new queue
                _messages = new Queue<ChatMessage>();
                if (eventArgument.Trim().Length > 0)
                {
                    ChatMessage Msg = new ChatMessage(1, System.DateTime.Now, ModifiedMsgText, this.UserName);
                    _messages.Enqueue(Msg);
                }
                Context.Application.Lock();
                Context.Application[WebChatTopic] = _messages;
                Context.Application[WebChatTopic + "_MAX"] = 1;
                Context.Application.UnLock();
            }
            else //existing Queue found, so use it
            {
                Context.Application.Lock();
                _messages = ((Queue<ChatMessage>)(Context.Application[WebChatTopic]));
                if (eventArgument.Trim().Length > 0)
                {
                    long Max = Convert.ToInt64(Context.Application[WebChatTopic + "_MAX"]);
                    Max += 1;
                    ChatMessage Msg = new ChatMessage(Max, DateTime.Now, ModifiedMsgText, this.UserName);

                    //keep track of the max message id
                    Context.Application[WebChatTopic + "_MAX"] = Max;

                    //place new message on the stack
                    _messages.Enqueue(Msg);

                    //purge stale messages
                    if (_messages.Count > MessageStoreLimit)
                    {
                        Msg = _messages.Dequeue();
                    }

                }

                //put the queue back into application state
                Context.Application[WebChatTopic] = _messages;
                Context.Application.UnLock();
            }
        }

        #endregion

        #region ICallbackEventHandler Members

        public string GetCallbackResult()
        {
            string retval = "";

            StringBuilder sb = new StringBuilder();
            foreach (ChatMessage Msg in _messages)
            {
                if (Msg.MessageId > _lastMsgID)
                {
                    //Build the return string 
                    //containing recent chat messages
                    sb.Append(Msg.MessageId.ToString());
                    sb.Append("||"); //column delimiter
                    sb.Append(Msg.MessageText);
                    //sb.Append("||") 'column delimiter
                    //sb.Append(Msg.MessageDateTime.ToString)
                    //sb.Append("||") 'column delimiter
                    //sb.Append(Msg.MessageSender)
                    sb.Append("~~"); //row delimeter
                }
            }
            retval = sb.ToString();
            return retval;
        }

        #endregion
    }
}
